Python Scrapy爬虫爬取抖音指定用户所有作品

写在前面

分析过程

刚开始本来要写爬那种正常给我们看的那种随机的视频的,但是后面抓包发现,他有5个必不可少的参数,而且链接都是访问几次或者时间过一会就会失效的,只能更换这5个参数再继续访问,可是通过反编译他的apk包发现只能推出他里面的两个参数,都是时间,但是两个是以不同的方式获取的时间,一个是10位数的,一个是13位数的。还有三个参数是通过其他方式加密的,由于本人对于反编译不是很精通,就没有分析出来,但是网上分析出来的人也很少,很多人分析到要用到两个key值加密时就放弃了,说是怕泄露了人家的商业机密,还有个别大佬,直接将分析出来的东西放在服务器上,然后做个接口给别人卖。我就放弃了爬这种随机的视频,后面就转去爬指定的用户的全部视频,我看了下,网上有很多教程,但是失效的也很多,而且我看用scrapy框架的也很少,所以我当复习下scrapy框架的知识的想法,就给大家写了一个。首先抓了下个人界面的链接发现还是要那5个参数的,然后在个人界面又发现了分享个人名片,以链接的形式,打开这个链接能发现个人的视频都在这里面,就从这里下手,点击它的视频是可以播放的说明视频链接就在这里面(这就比较好办了),调试一下发现它视频的链接在一个json列表列表里面,该列表也是要访问链接来获取的,不过大家懂的,该链接肯定又会有加密参数,不过js的加密的话还是可以分析的,首先就是user_id参数,这个看名字就知道是用户的id,这个在网页最下方的js里面就有,还有个dytk参数,界面里面也有,max_cursor参数刚开始是0,主要是用来判断是否下拉后还有视频,最后一个参数是_signature,这个参数是通过js加密出来的,全局搜索这个参数,发现是__M.require(“douyin_falcon:node_modules/byted-acrawler/dist/runtime”)里面出来的,其实此时你就可以在控制台获取到了

1
2
3
mysignature = __M.require("douyin_falcon:node_modules/byted-acrawler/dist/runtime")
signaturelog = mysignature.sign(user_id)
console.log(signaturelog)

在控制台就能显示_signature参数了,再往下你可以将加密的js提取出来,继续全局搜索__M就能看见t.__M.define = n,t.__M.require = e这代码的时候你就找到了,将这个js内容拷贝出来(提取关键内容也可以),然后和刚开始获取到的__M.define(“douyin_falcon:node_modules/byted-acrawler/dist/runtime”,function(l,e){…放在一个HTML文件中,再将上面的控制台语句也加进去,打开该文件你就能在控制台看到参数了,当然你也可以将这些东西处理成一个js文件,然后用node去执行,为了程序的方便我就用一位大佬的js文件来写我的代码了(时间有限,没有做自己的js),参数都齐全了之后,将这些参数拼接成链接去直接访问的话,还是得不到,必须以手机头部信息去访问,所以将chrome浏览器调试的时候选择成手机就可以访问到了,到此就结束了分析部分,可以动手写代码了。

使用说明

在抖音的个人主页,点击右上角的菜单,然后点击分享个人名片,然后选择以链接形式分享到微信或者QQ,然后复制该链接到爬虫文件夹的url_list.txt文件中,如果要爬取多人的,每个链接换行就好了。

程序源码

爬虫文件 douyinvideo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import scrapy
import requests
import re
import os
import json
from scrapy.utils.project import get_project_settings
class DouyinvideoSpider(scrapy.Spider):
name = 'douyinvideo'
allowed_domains = ['http://v.douyin.com','https://www.iesdouyin.com','https://www.amemv.com','http://*.ixigua.com',]
# get_json_url = "https://www.amemv.com/aweme/v1/aweme/post/?{0}"#获取json数据的链接,有的链接已经失效。
# new_ie_url = "https://www.douyin.com/aweme/v1/aweme/favorite/?{0}"
new_ie_url = "https://www.iesdouyin.com/aweme/v1/aweme/post/?{0}"
# url = "http://v.douyin.com/dXUNSp/"#有的人说短链接会重定向到长链接,但scrapy会自动重定向的
url_num = 1 # 定位url的数目
start_urls = []
url_file = open("url_list.txt", 'rU') # 取文件中的第一个url
try:
for line in url_file:
start_urls.append(line.rstrip('\n'))
break
finally:
url_file.close()
uid = None
dytk = None
video_link_arr = []
video_title_arr = []
def_max_cursor = '0'
_signature = ''
video_num = 1
def parse(self, response):
self.uid = None
self.dytk = None
self.video_link_arr = []
self.video_title_arr = []
self.def_max_cursor = '0'
self._signature = ''
self.video_num = 1
self.uid = str(re.findall('uid: "(.*)"',response.text)[0])
self.dytk = re.findall("dytk: '(.*)'",response.text)
self._signature = self.get_signature(self.uid)
parms = self.get_parms(self.uid,self._signature,self.dytk[0],self.def_max_cursor)
new_url = self.get_newurl(self.new_ie_url,parms)
yield response.follow(new_url,callback = self.get_json,dont_filter=True,headers = get_project_settings().get('DEFAULT_REQUEST_HEADERS'))#filter=True 不去重/第一次探测start_url是否失效

def get_json(self,response):
_json = json.loads(response.text)
if not _json['aweme_list']:
parms = self.get_parms(self.uid, self._signature, self.dytk[0],self.def_max_cursor)
new_url = self.get_newurl(self.new_ie_url, parms)
yield response.follow(new_url,callback = self.get_json,dont_filter=True,headers = get_project_settings().get('DEFAULT_REQUEST_HEADERS'))#死循环访问链接,测试中总有一次会返回正确的json数据
else:
#获取到了第一批json数据
for _jsons in _json['aweme_list']:
print("正在获取抖音号%s的第%s条video数据........"%(self.uid,self.video_num))
self.video_num += 1
self.video_title_arr.append(_jsons['share_info']['share_desc'])
true_file_link = self.true_file_link(_jsons['video']['play_addr']['url_list'][0])
self.video_link_arr.append(true_file_link)
if _json['has_more']:
#翻页视频
self.def_max_cursor = str(_json['max_cursor'])
parms = self.get_parms(self.uid,self._signature,self.dytk[0],self.def_max_cursor)
new_url = self.get_newurl(self.new_ie_url, parms)
yield response.follow(new_url, callback=self.get_json, dont_filter=True,headers=get_project_settings().get('DEFAULT_REQUEST_HEADERS'))
else:#提交视频链接以及标题和用户id
yield {
'user_id':self.uid,
'file_name':self.video_title_arr,
'file_link':self.video_link_arr
}
next_url = self.next_url()
if next_url:
yield response.follow(next_url, callback=self.parse, dont_filter=True,headers=get_project_settings().get('DEFAULT_REQUEST_HEADERS'))
else:
pass

def get_signature(self,user_id):#获取signature参数
user_id = str(user_id)
_signature = os.popen('node _signature.js %s' % user_id)
_signature = _signature.readlines()[0]
_signature = _signature.replace("\n", "")
_signature = str(_signature)
return _signature

def get_parms(self,user_id,_signature,dytk,max_cursor):#获取链接参数表
parms = { # 参数字典
'user_id': user_id,
'count': '21',
'max_cursor': max_cursor,
'aid': '1128',
'_signature': _signature,
'dytk': dytk
}
return parms

def get_newurl(self,url,parms):#url组合
new_url = url.format('&'.join([Key + '=' + parms[Key] for Key in parms]))
return new_url

def true_file_link(self,url):#解决video的跳转链接
r = requests.get(url,headers = get_project_settings().get('DEFAULT_REQUEST_HEADERS'),allow_redirects = False)
return r.headers['Location']
def next_url(self):# 取一条没有被爬取过的链接
url_file = open("url_list.txt", 'rU')
try:
for (line,num) in zip(url_file,range(self.url_num+1)):
if num == self.url_num:
self.url_num += 1
return line.rstrip('\n')

else:
continue
finally:
url_file.close()
设置文件 settings.py (我只写我添加的设置,其他设置不变就好)
1
2
3
4
5
6
7
8
9
10
11
12
VIDEO_DIR = 'E:/Python/myScrapyProject/douyin/video/'#下载文件的保存路径
DEFAULT_REQUEST_HEADERS = {
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'upgrade-insecure-requests': '1',
'user-agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
}#请求链接的头设置
ITEM_PIPELINES = {
'douyin.pipelines.DouyinPipeline': 300,
}#开启ITEM_PIPELINES
itmes.py
1
2
3
user_id = scrapy.Field()
file_name = scrapy.Field()
file_link = scrapy.Field()
pipelines.py (数据存储以及视频下载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import urllib.request
from scrapy.utils.project import get_project_settings
import os
import re
class DouyinPipeline(object):
def process_item(self, item, spider):
# print(item['user_id'],item['file_name'],item['file_link'])
if_mkdir = os.path.exists(get_project_settings().get('VIDEO_DIR')+item['user_id'])#判断目录是否存在
if not if_mkdir:
os.makedirs(get_project_settings().get('VIDEO_DIR')+item['user_id'])#创建目录
def callbackfunc(blocknum, blocksize, totalsize):
percent = 100.0 * blocknum * blocksize / totalsize
if percent > 100:
percent = 100
num = int(percent)
print('\r' + "[" + "#" * num + "]" + "%.2f%%" % percent)
else:
num = int(percent)
print( '\r'+"["+"#"*num+"]"+"%.2f%%" % percent,end="")
num = 1
for (name,link) in zip(item['file_name'],item['file_link']):
#/ \ : * " < > | ?windows文件名中不能包含这些字符
name = re.sub('[/\:*"<>|?]','',name)
print("开始下载抖音号为%s第%s/%s文件->%s%s"%(item['user_id'],num,len(item['file_name']),"\t",name))
a, b = urllib.request.urlretrieve(link,get_project_settings().get('VIDEO_DIR') + item['user_id'] + "/" + str(num) +"-"+ name + ".mp4", callbackfunc)
num += 1
print("\n" + "抖音号为%s的视频已全部下载完成!"%(item['user_id']))
return item
感谢您的阅读,本文由 ZhangAo`s Blog 版权所有。如若转载,请注明出处:ZhangAo`s Blog(https://www.imzhangao.com/2018/10/04/Python Scrapy爬虫爬取抖音指定用户所有作品/
Python破解百度云限速
一个基于jQuery的简单轮播图效果